with ada.Exceptions;
with Ada.Text_IO;

package body Sigma is

   -----------
   -- Creer --
   -----------

   function Creer return Modele is
   begin
      return new Modele_Term'(The_Builder => Creer);
   end Creer;

   --------------
   -- Inscrire --
   --------------

   function Inscrire (c : Modele; i : Individu) return Modele is
   begin
      return new Modele_Term'(The_Builder => Inscrire, Inscrire_c => c, Inscrire_i => new Individu'(i));
   end Inscrire;

   -------------
   -- Bloquer --
   -------------

   function Bloquer
     (c : Modele;
      i : Individu;
      j : Individu)
      return Modele
   is
   begin
      return new Modele_Term'(The_Builder => Bloquer,
                              Bloquer_c => c,
                              Bloquer_i => new Individu'(i),
                              Bloquer_j => new Individu'(j));
   end Bloquer;

   ---------------
   -- Debloquer --
   ---------------

   function Debloquer
     (c : Modele;
      i : Individu;
      j : Individu)
      return Modele
   is
   begin
      return new Modele_Term'(The_Builder => Debloquer,
                              Debloquer_c => c,
                              Debloquer_i => new Individu'(i),
                              Debloquer_j => new Individu'(j));

   end Debloquer;

   --------------------
   -- Ajouter_cercle --
   --------------------

   function Ajouter_cercle
     (c : Modele;
      i : Individu;
      u : Cercle)
      return Modele
   is
   begin
      return new Modele_Term'(The_Builder => Ajouter_cercle,
                              Ajouter_cercle_c => c,
                              Ajouter_cercle_i => new Individu'(i),
                              Ajouter_cercle_u => new Cercle'(u));
   end Ajouter_cercle;

   --------------------
   -- Retirer_cercle --
   --------------------

   function Retirer_cercle
     (c : Modele;
      i : Individu;
      u : Cercle)
      return Modele
   is
   begin
      return new Modele_Term'(The_Builder => Retirer_cercle,
                              Retirer_cercle_c => c,
                              Retirer_cercle_i => new Individu'(i),
                              Retirer_cercle_u => new Cercle'(u));
   end Retirer_cercle;

   --------------------
   -- Ajouter_membre --
   --------------------

   function Ajouter_membre
     (c : Modele;
      u : Cercle;
      i : Individu;
      j : Individu)
      return Modele
   is
   begin
      return new Modele_Term'(The_Builder => Ajouter_membre,
                              Ajouter_membre_c => c,
                              Ajouter_membre_u => new Cercle'(u),
                              Ajouter_membre_i => new Individu'(i),
                              Ajouter_membre_j => new Individu'(j));
   end Ajouter_membre;

   --------------------
   -- Retirer_membre --
   --------------------

   function Retirer_membre
     (c : Modele;
      u : Cercle;
      i : Individu;
      j : Individu)
      return Modele
   is
   begin
      return new Modele_Term'(The_Builder => Retirer_membre,
                              Retirer_membre_c => c,
                              Retirer_membre_u => new Cercle'(u),
                              Retirer_membre_i => new Individu'(i),
                              Retirer_membre_j => new Individu'(j));
   end Retirer_membre;

   ------------
   -- Poster --
   ------------

   function Poster
     (c : Modele;
      i : Individu;
      u : Cercle;
      t : Texte)
      return Modele
   is
   begin
      return new Modele_Term'(The_Builder => Poster,
                              Poster_c => c,
                              Poster_i => new Individu'(i),
                              Poster_u => new Cercle'(u),
                              Poster_t => new Texte'(t));
   end Poster;

   ---------------
   -- Commenter --
   ---------------

   function Commenter
     (c : Modele;
      i : Individu;
      n : Natural;
      t : Texte)
      return Modele
   is
   begin
      return new Modele_Term'(The_Builder => Commenter,
                              Commenter_c => c,
                              Commenter_i => new Individu'(i),
                              Commenter_n => n,
                              Commenter_t => new Texte'(t));
   end Commenter;

   -----------------
   -- Est_inscrit --
   -----------------

   function Est_inscrit (c : Modele; i : Individu) return Boolean is
   begin
      case c.The_Builder is
         when Creer =>
            return false;
         when Inscrire =>
            if c.Inscrire_i.all = i then
               return true;
            else
               return Est_inscrit(c.Inscrire_c, i);
            end if;
         when Bloquer =>
            return Est_inscrit(c.Bloquer_c, i);
         when Debloquer =>
            return Est_inscrit(c.Debloquer_c, i);
         when Ajouter_cercle =>
            return Est_inscrit(c.Ajouter_cercle_c, i);
         when Retirer_cercle =>
            return Est_inscrit(c.Retirer_cercle_c, i);
         when Ajouter_membre =>
            return Est_inscrit(c.Ajouter_membre_c, i);
         when Retirer_membre =>
            return Est_inscrit(c.Retirer_membre_c, i);
         when Poster =>
            return Est_inscrit(c.Poster_c, i);
         when Commenter =>
            return Est_inscrit(c.Commenter_c, i);
      end case;
   end Est_inscrit;

   ---------------------
   -- Nombre_inscrits --
   ---------------------

   function Nombre_inscrits (c : Modele) return Natural is
   begin
      case c.The_Builder is
         when Creer =>
            return 0;
         when Inscrire =>
            return Nombre_inscrits(c.Inscrire_c) + 1;
         when Bloquer =>
            return Nombre_inscrits(c.Bloquer_c);
         when Debloquer =>
            return Nombre_inscrits(c.Debloquer_c);
         when Ajouter_cercle =>
            return Nombre_inscrits(c.Ajouter_cercle_c);
         when Retirer_cercle =>
            return Nombre_inscrits(c.Retirer_cercle_c);
         when Ajouter_membre =>
            return Nombre_inscrits(c.Ajouter_membre_c);
         when Retirer_membre =>
            return Nombre_inscrits(c.Retirer_membre_c);
         when Poster =>
            return Nombre_inscrits(c.Poster_c);
         when Commenter =>
            return Nombre_inscrits(c.Commenter_c);
      end case;
   end Nombre_inscrits;

   ------------------
   -- Ieme_inscrit --
   ------------------

   function Ieme_inscrit (c : Modele; n : Natural) return Individu is
   begin
      case c.The_Builder is
         when Creer =>
            Raise Program_Error;

         when Inscrire =>
            if n <= Nombre_inscrits(c.Inscrire_c) then
               if Ieme_inscrit(c.Inscrire_c, n) <= c.Inscrire_i.all then return Ieme_inscrit(c.Inscrire_c, n);
               else if n = 1 then return c.Inscrire_i.all;
                  else if Ieme_inscrit(c.Inscrire_c, n - 1) <= c.Inscrire_i.all then return c.Inscrire_i.all;
                     else return Ieme_inscrit(c.Inscrire_c, n - 1);
                     end if;
                  end if;
               end if;
            else if n = 1 then return c.Inscrire_i.all;
               else if Ieme_inscrit(c.Inscrire_c, n - 1) <= c.Inscrire_i.all then return c.Inscrire_i.all;
                  else return Ieme_inscrit(c.Inscrire_c, n - 1);
                  end if;
               end if;
            end if;
         when Bloquer =>
            return Ieme_inscrit(c.Bloquer_c, n);
         when Debloquer =>
            return Ieme_inscrit(c.Debloquer_c, n);
         when Ajouter_cercle =>
            return Ieme_inscrit(c.Ajouter_cercle_c, n);
         when Retirer_cercle =>
            return Ieme_inscrit(c.Retirer_cercle_c, n);
         when Ajouter_membre =>
            return Ieme_inscrit(c.Ajouter_membre_c, n);
         when Retirer_membre =>
            return Ieme_inscrit(c.Retirer_membre_c, n);
         when Poster =>
            return Ieme_inscrit(c.Poster_c, n);
         when Commenter =>
            return Ieme_inscrit(c.Commenter_c, n);
      end case;
   end Ieme_inscrit;

   ----------------
   -- Est_bloque --
   ----------------

   function Est_bloque
     (c : Modele;
      i : Individu;
      j : Individu)
      return Boolean
   is
   begin
      case c.The_Builder is
         when Creer =>
            return false;
         when Inscrire =>
            return Est_bloque(c.Inscrire_c, i, j);
         when Bloquer =>
            if i = c.Bloquer_i.all and j = c.Bloquer_j.all then
               return true;
            else
               return Est_bloque(c.Bloquer_c, i, j);
            end if;
         when Debloquer =>
            if i = c.Debloquer_i.all and j = c.Debloquer_j.all then
               return false;
            else
               return Est_bloque(c.Debloquer_c, i, j);
            end if;
         when Ajouter_cercle =>
            return Est_bloque(c.Ajouter_cercle_c, i, j);
         when Retirer_cercle =>
            return Est_bloque(c.Retirer_cercle_c, i, j);
         when Ajouter_membre =>
            return Est_bloque(c.Ajouter_membre_c, i, j);
         when Retirer_membre =>
            return Est_bloque(c.Retirer_membre_c, i, j);
         when Poster =>
            return Est_bloque(c.Poster_c, i, j);
         when Commenter =>
            return Est_bloque(c.Commenter_c, i, j);
      end case;
   end Est_bloque;

   -------------------
   -- Est_cercle_de --
   -------------------

   function Est_cercle_de
     (c : Modele;
      i : Individu;
      u : Cercle)
      return Boolean
   is
   begin
      case c.The_Builder is
         when Creer =>
            return false;
         when Inscrire =>
            if c.Inscrire_i.all = i then
               return false;
            else
               return Est_cercle_de(c.Inscrire_c, i, u);
            end if;
         when Bloquer =>
            return Est_cercle_de(c.Bloquer_c, i, u);
         when Debloquer =>
            return Est_cercle_de(c.Debloquer_c, i, u);
         when Ajouter_cercle =>
            if c.Ajouter_cercle_i.all = i and c.Ajouter_cercle_u.all = u then
               return true;
            else
               return Est_cercle_de(c.Ajouter_cercle_c, i, u);
            end if;
         when Retirer_cercle =>
            if c.Retirer_cercle_i.all = i and c.Retirer_cercle_u.all = u then
               return false;
            else
               return Est_cercle_de(c.Retirer_cercle_c, i, u);
            end if;
         when Ajouter_membre =>
            return Est_cercle_de(c.Ajouter_membre_c, i, u);
         when Retirer_membre =>
            return Est_cercle_de(c.Retirer_membre_c, i, u);
         when Poster =>
            return Est_cercle_de(c.Poster_c, i, u);
         when Commenter =>
            return Est_cercle_de(c.Commenter_c, i, u);
      end case;
   end Est_cercle_de;

   --------------------
   -- Nombre_cercles --
   --------------------

   function Nombre_cercles (c : Modele; i : Individu) return Natural is
   begin
      case c.The_Builder is
         when Creer =>
            return 0;
         when Inscrire =>
            return Nombre_cercles(c.Inscrire_c,i);
         when Bloquer =>
            return Nombre_cercles(c.Bloquer_c,i);
         when Debloquer =>
            return Nombre_cercles(c.Debloquer_c,i);
         when Ajouter_cercle =>
            if i = c.Ajouter_cercle_i.all then
               return Nombre_cercles(c.Ajouter_cercle_c,i) + 1;
            else
               return Nombre_cercles(c.Ajouter_cercle_c,i);
            end if;
         when Retirer_cercle =>
            if i = c.Retirer_cercle_i.all then
               return Nombre_cercles(c.Retirer_cercle_c, i) - 1;
            else
               return Nombre_cercles(c.Retirer_cercle_c, i);
            end if;
         when Ajouter_membre =>
            return Nombre_cercles(c.Ajouter_membre_c,i);
         when Retirer_membre =>
            return Nombre_cercles(c.Retirer_membre_c,i);
         when Poster =>
            return Nombre_cercles(c.Poster_c,i);
         when Commenter =>
            return Nombre_cercles(c.Commenter_c,i);
      end case;
   end Nombre_cercles;

   -----------------
   -- Ieme_cercle --
   -----------------

   function Ieme_cercle
     (c : Modele;
      n : Natural;
      i : Individu)
      return Cercle
   is
   begin
      case c.The_Builder is
         when Creer =>
            raise Program_Error;
         when Inscrire =>
            return Ieme_cercle(c.Inscrire_c, n, i);
         when Bloquer =>
            return Ieme_cercle(c.Bloquer_c, n, i);
         when Debloquer =>
            return Ieme_cercle(c.Debloquer_c, n, i);
         when Ajouter_cercle =>
            if c.Ajouter_cercle_i.all = i then
               if n <= Nombre_cercles(c.Ajouter_cercle_c, i) then
                  if Ieme_cercle(c.Ajouter_cercle_c, n, i) <= c.Ajouter_cercle_u.all then return Ieme_cercle(c.Ajouter_cercle_c, n, i);
                  else if n = 1 then return c.Ajouter_cercle_u.all;
                     else if Ieme_cercle(c.Ajouter_cercle_c, n - 1, i) <= c.Ajouter_cercle_u.all then return c.Ajouter_cercle_u.all;
                        else return Ieme_cercle(c.Ajouter_cercle_c, n - 1, i);
                        end if;
                     end if;
                  end if;
               else if n = 1 then return c.Ajouter_cercle_u.all;
                  else if Ieme_cercle(c.Ajouter_cercle_c, n - 1, i) <= c.Ajouter_cercle_u.all then return c.Ajouter_cercle_u.all;
                     else return Ieme_cercle(c.Ajouter_cercle_c, n, i);
                     end if;
                  end if;
               end if;
            else
               return Ieme_cercle(c.Ajouter_cercle_c, n, i);
            end if;
         when Retirer_cercle =>
            if c.Retirer_cercle_i.all = i then
               if c.Retirer_cercle_u.all <= Ieme_cercle(c.Retirer_cercle_c, n, i) then
                  return Ieme_cercle(c.Retirer_cercle_c, n + 1, i);
               else
                  return Ieme_cercle(c.Retirer_cercle_c, n, i);
               end if;
            else
               return Ieme_cercle(c.Retirer_cercle_c, n, i);
            end if;
         when Ajouter_membre =>
            return Ieme_cercle(c.Ajouter_membre_c, n , i);
         when Retirer_membre =>
            return Ieme_cercle(c.Retirer_membre_c, n , i);
         when Poster =>
            return Ieme_cercle(c.Poster_c, n , i);
         when Commenter =>
            return Ieme_cercle(c.Commenter_c, n , i);
      end case;
   end Ieme_cercle;

   -------------------
   -- Est_membre_de --
   -------------------

   function Est_membre_de
     (c : Modele;
      u : Cercle;
      i : Individu;
      j : Individu)
      return Boolean
   is
   begin
      case c.The_Builder is
         when Creer =>
            return false;
         when Inscrire =>
            return Est_membre_de(c.Inscrire_c, u, i, j);
         when Bloquer =>
            if c.Bloquer_i.all = i and c.Bloquer_j.all = j then
               return false;
            else
               return Est_membre_de(c.Bloquer_c, u, i, j);
            end if;
         when Debloquer =>
            return Est_membre_de(c.Debloquer_c, u, i, j);
         when Ajouter_cercle =>
            if c.Ajouter_cercle_i.all = i and c.Ajouter_cercle_u.all = u then
               return false;
            else
               return Est_membre_de(c.Ajouter_cercle_c, u, i, j);
            end if;
         when Retirer_cercle =>
            if c.Retirer_cercle_i.all = i and c.Retirer_cercle_u.all = u then
               return false;
            else
               return Est_membre_de(c.Retirer_cercle_c, u, i, j);
            end if;
         when Ajouter_membre =>
            if c.Ajouter_membre_i.all = i and c.Ajouter_membre_u.all = u and c.Ajouter_membre_j.all = j then
               return true;
            else
               return Est_membre_de(c.Ajouter_membre_c, u, i, j);
            end if;
         when Retirer_membre =>
            if c.Retirer_membre_i.all = i and c.Retirer_membre_u.all = u and c.Retirer_membre_j.all = j then
               return false;
            else
               return Est_membre_de(c.Retirer_membre_c, u, i, j);
            end if;
         when Poster =>
            return Est_membre_de(c.Poster_c, u, i, j);
         when Commenter =>
            return Est_membre_de(c.Commenter_c, u, i, j);
      end case;
   end Est_membre_de;

   --------------------
   -- Nombre_membres --
   --------------------

   function Nombre_membres
     (c : Modele;
      i : Individu;
      u : Cercle)
      return Natural
   is
   begin
      case c.The_Builder is
         when Creer =>
            return 0;
         when Inscrire =>
            return Nombre_membres(c.Inscrire_c,i,u);
         when Bloquer =>
            if c.Bloquer_i.all = i and est_membre_de (c.Bloquer_c, u, i, c.Bloquer_j.all) then
               return Nombre_membres (c.Bloquer_c, i, u) - 1;
            else
               return Nombre_membres (c.Bloquer_c, i, u);
            end if;
         when Debloquer =>
            return Nombre_membres(c.Debloquer_c, i, u);
         when Ajouter_cercle =>
            if c.Ajouter_cercle_i.all = i and c.Ajouter_cercle_u.all = u then
               return 0;
            else
               return Nombre_membres(c.Ajouter_cercle_c,i,u);
            end if;
         when Retirer_cercle =>
            if c.Retirer_cercle_i.all = i and  c.Retirer_cercle_u.all = u then
               return 0;
            else
               return Nombre_membres(c.Retirer_cercle_c,i,u);
            end if;
         when Ajouter_membre =>
            if c.Ajouter_membre_i.all = i and c.Ajouter_membre_u.all = u then
               return Nombre_membres (c.Ajouter_membre_c, i, u) + 1;
            else
               return Nombre_membres (c.Ajouter_membre_c, i, u);
            end if;
         when Retirer_membre =>
            if c.Retirer_membre_i.all = i and c.Retirer_membre_u.all = u then
               return Nombre_membres (c.Retirer_membre_c, i, u) - 1;
            else
               return Nombre_membres (c.Retirer_membre_c, i, u);
            end if;
         when Poster =>
            return Nombre_membres(c.Poster_c, i, u);
         when Commenter =>
            return Nombre_membres(c.Commenter_c, i, u);
      end case;
   end Nombre_membres;

   -----------------
   -- Ieme_membre --
   -----------------

   function Ieme_membre
     (c : Modele;
      n : Natural;
      i : Individu;
      u : Cercle)
      return Individu
   is
   begin
      case c.The_Builder is
         when Creer =>
            raise Program_Error;
         when Inscrire =>
            return Ieme_membre(c.Inscrire_c, n, i, u);
         when Bloquer =>
            if i = c.Bloquer_i.all and Est_membre_de(c.Bloquer_c, u, i, c.Bloquer_j.all) then
               if c.Bloquer_j.all <= Ieme_membre(c.Bloquer_c, n, i, u) then
                  return Ieme_membre(c.Bloquer_c, n + 1, i, u);
               else
                  return Ieme_membre(c.Bloquer_c, n, i, u);
               end if;
            else
               return Ieme_membre(c.Bloquer_c, n, i, u);
            end if;
         when Debloquer =>
            return Ieme_membre(c.Debloquer_c, n, i, u);
         when Ajouter_cercle =>
            return Ieme_membre(c.Ajouter_cercle_c, n, i, u);
         when Retirer_cercle =>
            return Ieme_membre(c.Retirer_cercle_c, n, i, u);
         when Ajouter_membre =>
            if i = c.Ajouter_membre_i.all and u = c.Ajouter_membre_u.all then
               if n <= Nombre_membres(c.Ajouter_membre_c, i, u) then
                  if Ieme_membre(c.Ajouter_membre_c, n, i, u) <= c.Ajouter_membre_j.all then return Ieme_membre(c.Ajouter_membre_c, n, i, u);
                  else if n = 1 then return c.Ajouter_membre_j.all;
                     else if Ieme_membre(c.Ajouter_membre_c, n - 1, i, u) <= c.Ajouter_membre_j.all then return c.Ajouter_membre_j.all;
                        else return Ieme_membre(c.Ajouter_membre_c, n - 1, i, u);
                        end if;
                     end if;
                  end if;
               else if n = 1 then return c.Ajouter_membre_j.all;
                  else if Ieme_membre(c.Ajouter_membre_c, n - 1, i, u) <= c.Ajouter_membre_j.all then return c.Ajouter_membre_j.all;
                     else return Ieme_membre(c.Ajouter_membre_c, n - 1, i, u);
                     end if;
                  end if;
               end if;
            else
                return Ieme_membre(c.Ajouter_membre_c, n, i, u);
            end if;
         when Retirer_membre =>
            if i = c.Ajouter_cercle_i.all and Est_membre_de(c.Retirer_membre_c, u, i, c.Retirer_membre_j.all) then
               if c.Retirer_membre_j.all <= Ieme_membre(c.Retirer_membre_c, n, i, u) then
                  return Ieme_membre(c.Retirer_membre_c, n + 1, i, u);
               else
                  return Ieme_membre(c.Retirer_membre_c, n, i, u);
               end if;
            else
               return Ieme_membre(c.Retirer_membre_c, n, i, u);
            end if;
         when Poster =>
            return Ieme_membre(c.Poster_c, n, i, u);
         when Commenter =>
            return Ieme_membre(c.Commenter_c, n, i, u);
      end case;
   end Ieme_membre;

   ------------------
   -- Nombre_posts --
   ------------------

   function Nombre_posts (c : Modele) return Natural is
   begin
      case c.The_Builder is
         when Creer =>
            return 0;
         when Inscrire =>
            return Nombre_posts(c.Inscrire_c);
         when Bloquer =>
            return Nombre_posts(c.Bloquer_c);
         when Debloquer =>
            return Nombre_posts(c.Debloquer_c);
         when Ajouter_cercle =>
            return Nombre_posts(c.Ajouter_cercle_c);
         when Retirer_cercle =>
            return Nombre_posts(c.Retirer_cercle_c);
         when Ajouter_membre =>
            return Nombre_posts(c.Ajouter_membre_c);
         when Retirer_membre =>
            return Nombre_posts(c.Retirer_membre_c);
         when Poster =>
            return Nombre_posts(c.Poster_c) + 1;
         when Commenter =>
            return Nombre_posts(c.Commenter_c);
      end case;
   end Nombre_posts;

   ---------------
   -- Consulter --
   ---------------

   function Consulter (c : Modele; i : Individu) return Mur is
   begin
      case c.The_Builder is
         when Creer =>
            raise Program_Error;
         when Inscrire =>
            if c.Inscrire_i.all = i then
               return Creer(i);
            else
               return Consulter(c.Inscrire_c, i);
            end if;
         when Bloquer =>
            return Consulter(c.Bloquer_c, i);
         when Debloquer =>
            return Consulter(c.Debloquer_c, i);
         when Ajouter_cercle =>
            return Consulter(c.Ajouter_cercle_c, i);
         when Retirer_cercle =>
            return Consulter(c.Retirer_cercle_c, i);
         when Ajouter_membre =>
            return Consulter(c.Ajouter_membre_c, i);
         when Retirer_membre =>
            return Consulter(c.Retirer_membre_c, i);
         when Poster =>
            if (Est_membre_de(c.Poster_c, c.Poster_u.all, c.Poster_i.all, i) and not Est_bloque(c.Poster_c, c.Poster_i.all, i)) or c.Poster_i.all = i then
               return Afficher(Consulter(c.Poster_c, i), c.Poster_i.all, c.Poster_t.all, Nombre_posts(c.Poster_c) + 1);
            else
               return Consulter(c.Poster_c, i);
            end if;
         when Commenter =>
            if Est_defini(Consulter(c.Commenter_c, i), c.Commenter_n) and ((i = c.Commenter_i.all) or not (Est_bloque(c.Commenter_c, Auteur(Consulter(c.Commenter_c, i), c.Commenter_n), c.Commenter_i.all) or Est_bloque(c.Commenter_c, c.Commenter_i.all, i) or Est_bloque(c.Commenter_c, i, c.Commenter_i.all))) then
               return Afficher(Consulter(c.Commenter_c, i), c.Commenter_i.all, c.Commenter_t.all, c.Commenter_n);
            else
               return Consulter(c.Commenter_c, i);
            end if;
      end case;
   end Consulter;

end Sigma;
